﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Abp.Application.Services;
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Authorization.Roles;
using Abp.Authorization.Users;
using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.Configuration;
using Abp.IdentityFramework;
using Abp.Linq.Extensions;
using Abp.Localization;
using Abp.Runtime.Session;
using Abp.UI;
using Abp.Zero.Configuration;
using pandx.Mulan.Authorization;
using pandx.Mulan.Authorization.Accounts;
using pandx.Mulan.Authorization.Roles;
using pandx.Mulan.Authorization.Users;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using pandx.Mulan.Authorization.Roles.Dto;
using pandx.Mulan.Authorization.Users.Dto;
using System.Linq.Dynamic.Core;
using Abp.Notifications;
using Abp.Organizations;
using Castle.DynamicProxy.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using pandx.Mulan.Authorization.Permissions;
using pandx.Mulan.Authorization.Permissions.Dto;
using pandx.Mulan.Authorization.Users.Exporting;
using pandx.Mulan.Dto;
using pandx.Mulan.Flows;
using pandx.Mulan.Notifications;
using pandx.Mulan.OrganizationUnits.Dto;

namespace pandx.Mulan.Authorization.Users
{
    public class UserAppService : MulanAppServiceBase,IUserAppService
    {
        private readonly IRepository<Role> _roleRepository;
        private readonly IPasswordHasher<User> _passwordHasher;
        private readonly LogInManager _logInManager;
        private readonly IRoleManagementConfig _roleManagementConfig;
        private readonly IRepository<UserRole,long> _userRoleRepository;
        private readonly IRepository<UserPermissionSetting, long> _userPermissionRepository;
        private readonly IRepository<RolePermissionSetting, long> _rolePermissionRepository;
        private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
        private readonly IRepository<UserOrganizationUnit, long> _userOrganizationUnitRepository;
        private readonly IRepository<OrganizationUnitRole, long> _organizationUnitRoleRepository;
        private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
        private readonly IUserListExcelExporter _userListExcelExporter;
        private readonly INotificationSubscriptionManager _notificationSubscriptionManager;
        private readonly IAppNotifier _appNotifier;
        
        public UserAppService(
            IRepository<User, long> repository,
            IRepository<Role> roleRepository,
            IPasswordHasher<User> passwordHasher,
            LogInManager logInManager,
            IRoleManagementConfig roleManagementConfig,
            IRepository<UserRole,long> userRoleRepository,
            IRepository<UserPermissionSetting,long> userPermissionRepository,
            IRepository<RolePermissionSetting,long> rolePermissionRepository,
            IRepository<OrganizationUnit,long> organizationUnitRepository,
            IRepository<UserOrganizationUnit,long> userOrganizationUnitRepository,
            IRepository<OrganizationUnitRole,long> organizationUnitRoleRepository,
            IEnumerable<IPasswordValidator<User>> passwordValidators,
            IUserListExcelExporter userListExcelExporter,
            INotificationSubscriptionManager notificationSubscriptionManager,
            IAppNotifier appNotifier)
        {
            _roleRepository = roleRepository;
            _passwordHasher = passwordHasher;
            _logInManager = logInManager;
            _roleManagementConfig = roleManagementConfig;
            _userRoleRepository = userRoleRepository;
            _userPermissionRepository = userPermissionRepository;
            _rolePermissionRepository = rolePermissionRepository;
            _organizationUnitRepository = organizationUnitRepository;
            _userOrganizationUnitRepository = userOrganizationUnitRepository;
            _organizationUnitRoleRepository = organizationUnitRoleRepository;
            _passwordValidators = passwordValidators;
            _userListExcelExporter = userListExcelExporter;
            _notificationSubscriptionManager = notificationSubscriptionManager;
            _appNotifier = appNotifier;
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Browse)]
        public async Task<PagedResultDto<UserListDto>> ObtainUsers(ObtainUsersInput input)
        {

            var query = ObtainUsersFilteredQuery(input);
            var userCount = await query.CountAsync();
            var userOrganizationUnits = await query.OrderBy(input.Sorting).PageBy(input).ToListAsync();
            var userListDtos = userOrganizationUnits.Select(item =>
            {
                
                var dto = ObjectMapper.Map<UserListDto>(item.User);
                dto.OrganizationUnits = ObjectMapper.Map<List<UserListOrganizationUnitDto>>(item.OrganizationUnits);
                return dto as UserListDto;
            }).ToList();
            await FillRoleNames(userListDtos);
           
            return new PagedResultDto<UserListDto>(userCount,userListDtos);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Export)]
        public async Task<FileDto> ObtainUsersToExcel(ObtainUsersInput input)
        {
            var query = ObtainUsersFilteredQuery(input);
            var userOrganizationUnits = await query.OrderBy(input.Sorting).ToListAsync();
            var userListDtos = userOrganizationUnits.Select(item =>
            {
                
                var dto = ObjectMapper.Map<UserListDto>(item.User);
                dto.OrganizationUnits = ObjectMapper.Map<List<UserListOrganizationUnitDto>>(item.OrganizationUnits);
                return dto as UserListDto;
            }).ToList();
            await FillRoleNames(userListDtos);


            return _userListExcelExporter.ExportToExcel(userListDtos);
        }

        public async Task CreateOrUpdateUser(CreateOrUpdateUserInput input)
        {
            if (input.User.Id.HasValue)
            {
                await UpdateUserAsync(input);
            }
            else
            {
               await CreateUserAsync(input);
            }
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Update)]
        protected virtual async Task UpdateUserAsync(CreateOrUpdateUserInput input)
        {
            var user = await UserManager.FindByIdAsync(input.User.Id.ToString());
            
            ObjectMapper.Map(input.User, user);
            user.Surname = input.User.Name;
            CheckErrors(await UserManager.UpdateAsync(user));

            if (input.SetRandomPassword)
            {
                var randomPassword = await UserManager.CreateRandomPassword();
                user.Password = _passwordHasher.HashPassword(user, randomPassword);
                input.User.Password = randomPassword;
            } 
            else if(!input.User.Password.IsNullOrEmpty())
            {
                await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
                CheckErrors(await UserManager.ChangePasswordAsync(user,input.User.Password));
            }

            
            CheckErrors(await UserManager.SetRolesAsync(user,input.AssignedRoleNames));
            await UserManager.SetOrganizationUnitsAsync(user, input.OrganizationUnits.ToArray());

            if (input.SendActivationEmail)
            {
                user.SetNewEmailConfirmationCode();
            }
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Create)]
        protected virtual async Task CreateUserAsync(CreateOrUpdateUserInput input)
        {
            if (AbpSession.TenantId.HasValue)
            {
            }

            var user = ObjectMapper.Map<User>(input.User);
            user.TenantId = AbpSession.TenantId;

          
            if (input.SetRandomPassword)
            {
                var randomPassword = await UserManager.CreateRandomPassword();
                user.Password = _passwordHasher.HashPassword(user, randomPassword);
                input.User.Password = randomPassword;
            }
            else if (!input.User.Password.IsNullOrEmpty())
            {
                await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
                foreach (var validator in _passwordValidators)
                {
                    CheckErrors(await validator.ValidateAsync(UserManager,user,input.User.Password));
                }

                user.Password = _passwordHasher.HashPassword(user, input.User.Password);
            }

            user.ShouldChangePasswordOnNextLogin = input.User.ShouldChangePasswordOnNextLogin;
            

            user.Surname = input.User.Name;
   
            user.Roles = new Collection<UserRole>();
            foreach(var roleName in input.AssignedRoleNames)
            {
                var role = await RoleManager.GetRoleByNameAsync(roleName);
                user.Roles.Add(new UserRole(AbpSession.TenantId,user.Id,role.Id));
            }
            CheckErrors(await UserManager.CreateAsync(user));

            await CurrentUnitOfWork.SaveChangesAsync();

     
            await _notificationSubscriptionManager.SubscribeToAllAvailableNotificationsAsync(user.ToUserIdentifier());
            await _appNotifier.WelcomeToTheApplicationAsync(user.ToUserIdentifier());

    
            await UserManager.SetOrganizationUnitsAsync(user, input.OrganizationUnits.ToArray());

            if (input.SendActivationEmail)
            {
                user.SetNewEmailConfirmationCode();
              
            }
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Permission)]
        public async Task<GetUserPermissionsForEditOutput> GetUserPermissionsForEdit(EntityDto<long> input)
        {
            var user = await UserManager.GetUserByIdAsync(input.Id);
            var permissions = PermissionManager.GetAllPermissions();
            var grantedPermissions = await UserManager.GetGrantedPermissionsAsync(user);
            return new GetUserPermissionsForEditOutput
            {
                Permissions = ObjectMapper.Map<List<FlatPermissionDto>>(permissions).OrderBy(p=>p.DisplayName).ToList(),
                GrantedPermissionNames = grantedPermissions.Select(p=>p.Name).ToList()
            };
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Permission)]
        public async Task ResetUserSpecificPermissions(EntityDto<long> input)
        {
            var user = await UserManager.GetUserByIdAsync(input.Id);
            await UserManager.ResetAllPermissionsAsync(user);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Permission)]
        public async Task UpdateUserPermissions(UpdateUserPermissionsInput input)
        {
            if (AbpSession.UserId == 1)
            {
                throw new UserFriendlyException("演示版不支持此功能，请选择其他用户测试");
            }

            var user = await UserManager.GetUserByIdAsync(input.Id);
            var grantedPermissions =
                PermissionManager.GetPermissionsFromNamesByValidating(input.GrantedPermissionNames);
            await UserManager.SetGrantedPermissionsAsync(user, grantedPermissions);
        }

        private async Task FillRoleNames(IReadOnlyCollection<UserListDto> userListDtos)
        {
            var userIds = userListDtos.Select(u => u.Id);
            var userRoles = await _userRoleRepository.GetAll()
                .Where(userRole => userIds.Contains(userRole.UserId))
                .Select(userRole => userRole).ToListAsync();
        
            var distinctRoleIds = userRoles.Select(userRole => userRole.RoleId).Distinct();
            foreach (var user in userListDtos)
            {
                var rolesOfUser = userRoles.Where(userRole => userRole.UserId == user.Id).ToList();
                user.Roles = ObjectMapper.Map<List<UserListRoleDto>>(rolesOfUser);
            }
        
            var roleNames=new Dictionary<int,string>();
            foreach (var roleId in distinctRoleIds )
            {
                var role = await RoleManager.FindByIdAsync(roleId.ToString());
                if (role != null)
                {
                    roleNames[roleId] = role.DisplayName;
                }
            }
        
            foreach (var userListDto in userListDtos)
            {
                foreach (var userListRoleDto in userListDto.Roles)
                {
                    if (roleNames.ContainsKey(userListRoleDto.RoleId))
                    {
                        userListRoleDto.RoleName = roleNames[userListRoleDto.RoleId];
                    }
                }
        
                userListDto.Roles = userListDto.Roles.Where(r => r.RoleName != null).OrderBy(r => r.RoleName).ToList();
            }
        }

        private IQueryable<dynamic> ObtainUsersFilteredQuery(ObtainUsersInput input)
        {
            if (input.Gender==null || input.Gender?.Count == 0)
            {
                input.Gender = new List<Gender> {Gender.Male, Gender.Female};
            }

            if (input.IsActive==null || input.IsActive?.Count == 0)
            {
                input.IsActive = new List<bool> {true, false};
            }
            var query = from user in UserManager.Users
                select new 
                {
                    User = user,
                    OrganizationUnits = from ouUser in user.OrganizationUnits
                        join ou in _organizationUnitRepository.GetAll() on ouUser.OrganizationUnitId equals ou.Id
                        select ou
                };
            query=query
            .Where(x=>x.User.CreationTime>=input.StartTime && x.User.CreationTime<=input.EndTime)
            .Where(x=>input.Gender.Contains(x.User.Gender))
            .Where(x=>input.IsActive.Contains(x.User.IsActive))
            .WhereIf(input.Role.HasValue, x => x.User.Roles.Any(r => r.RoleId == input.Role.Value))
            .WhereIf(input.OnlyLockedUsers.HasValue && input.OnlyLockedUsers.Value,
                x => x.User.LockoutEndDateUtc.HasValue && x.User.LockoutEndDateUtc.Value > DateTime.UtcNow)
            .WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.User.Name.Contains(input.Filter) ||
                                                              x.User.UserName.Contains(input.Filter) ||
                                                              x.User.EmailAddress.Contains(input.Filter)||
                                                              x.User.PhoneNumber.Contains(input.Filter)||
                                                              x.OrganizationUnits.Any(ou=>ou.DisplayName.Contains(input.Filter)));
            if (input.Permissions != null && input.Permissions.Any(p => !p.IsNullOrWhiteSpace()))
            {
                var staticRoleNames = _roleManagementConfig.StaticRoles.Where(
                        r => r.GrantAllPermissionsByDefault && r.Side == AbpSession.MultiTenancySide)
                    .Select(r => r.RoleName).ToList();
                input.Permissions = input.Permissions.Where(p => !string.IsNullOrEmpty(p)).ToList();
                var userIds = from x in query
                    join ur in _userRoleRepository.GetAll() on x.User.Id equals ur.UserId into urJoined
                    from ur in urJoined.DefaultIfEmpty()
                    join role in _roleRepository.GetAll() on ur.RoleId equals role.Id into roleJoined
                    from role in roleJoined.DefaultIfEmpty()
                    join up in _userPermissionRepository.GetAll()
                            .Where(userPermission => input.Permissions.Contains(userPermission.Name)) on x.User.Id equals
                        up.UserId into upJoined
                    from up in upJoined.DefaultIfEmpty()
                    join rp in _rolePermissionRepository.GetAll()
                        .Where(rolePermission => input.Permissions.Contains(rolePermission.Name)) on new
                        {RoleId = ur == null ? 0 : ur.RoleId} equals new {rp.RoleId} into rpJoined
                    from rp in rpJoined.DefaultIfEmpty()
                    where (up != null && up.IsGranted) ||
                          (up == null && rp != null && rp.IsGranted) ||
                          (up == null && rp == null && staticRoleNames.Contains(role.Name))
                    group x.User by x.User.Id
                    into userGrouped
                    select userGrouped.Key;
                query = query.Where(e => userIds.Contains(e.User.Id));
            }
            
            if (input.SameUnit.HasValue && input.SameUnit.Value && input.Creator.HasValue)
            {
                var uos=_userOrganizationUnitRepository.GetAll().Where(uo => uo.UserId == input.Creator.Value).Select(uo=>uo.OrganizationUnitId).ToList();
                query = query.Where(x => x.OrganizationUnits.Any(o => uos.Contains(o.Id)));
            }
           
            if (input.RestrictRoles!=null && input.RestrictRoles.Count > 0)
            {
                query = query.Where(x => x.User.Roles.Any(r => input.RestrictRoles.Contains(r.RoleId)));
            }
            
            return query;
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_ChangePassword)]
        public async Task<bool> ResetPassword(ResetPasswordDto input)
        {
            if (AbpSession.UserId == null)
            {
                throw new UserFriendlyException("重置密码之前必须登录");
            }
            long currentUserId = AbpSession.UserId.Value;
            var currentUser = await UserManager.GetUserByIdAsync(currentUserId);
            var loginAsync = await _logInManager.LoginAsync(currentUser.UserName, input.AdminPassword, shouldLockout: false);
            if (loginAsync.Result != AbpLoginResultType.Success)
            {
                throw new UserFriendlyException(currentUser.Name + "的密码错误，无法通过验证");
            }
            if (currentUser.IsDeleted || !currentUser.IsActive)
            {
                return false;
            }
            var roles = await UserManager.GetRolesAsync(currentUser);
            if (!roles.Contains(StaticRoleNames.Host.Admin))
            {
                throw new UserFriendlyException("只有系统管理员可以重置密码");
            }
            var user = await UserManager.GetUserByIdAsync(input.UserId);
            if (user != null)
            {
                user.Password = _passwordHasher.HashPassword(user, input.NewPassword);
                await CurrentUnitOfWork.SaveChangesAsync();
            }
            return true;
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Create,AppPermissions.Pages_Administration_Users_Update)]
        public async Task<GetUserForEditOutput> GetUserForEdit(NullableIdDto<long> input)
        {

       
            var userRoleDtos = await RoleManager.Roles.OrderBy(r => r.Name)
                .Select(r => new UserRoleDto
                {
                    RoleId=r.Id,
                    RoleName=r.Name
                })
                .ToArrayAsync();

            var allOrganizationUnits = await _organizationUnitRepository.GetAllListAsync();
            var output = new GetUserForEditOutput
            {
                Roles=userRoleDtos,
                MemberedOrganizationUnits = new List<string>()
            };

            if (!input.Id.HasValue)
            {
                output.User = new UserEditDto
                {
                    IsActive = true,
                    ShouldChangePasswordOnNextLogin = true,
                    IsLockoutEnabled = await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.UserLockOut.IsEnabled)
                };

                foreach (var defaultRole in await RoleManager.Roles.Where(r => r.IsDefault).ToListAsync())
                {
                    var defaultUserRole = userRoleDtos.FirstOrDefault(ur => ur.RoleName == defaultRole.Name);
                    if (defaultUserRole != null)
                    {
                        defaultUserRole.IsAssigned = true;
                    }
                }
            }
            else
            {
                var user = await UserManager.GetUserByIdAsync(input.Id.Value);

                output.User = ObjectMapper.Map<UserEditDto>(user);
                output.ProfilePictureId = user.ProfilePictureId;

                var organizationUnits = await UserManager.GetOrganizationUnitsAsync(user);
                output.MemberedOrganizationUnits = organizationUnits.Select(ou => ou.Code).ToList();

                var allRolesOfUsersOrganizationUnits = GetAllRoleNamesOfUsersOrganizationUnits(input.Id.Value);

                foreach (var userRoleDto in userRoleDtos)
                {
                    userRoleDto.IsAssigned = await UserManager.IsInRoleAsync(user, userRoleDto.RoleName);
                    userRoleDto.InheritedFromOrganizationUnit =
                        allRolesOfUsersOrganizationUnits.Contains(userRoleDto.RoleName);
                }
            }

            return output;
        }

        private List<string> GetAllRoleNamesOfUsersOrganizationUnits(long userId)
        {
            return (from userOu in _userOrganizationUnitRepository.GetAll()
                    join roleOu in _organizationUnitRoleRepository.GetAll() on userOu.OrganizationUnitId equals roleOu.OrganizationUnitId
                    join role in _roleRepository.GetAll() on roleOu.RoleId equals role.Id
                    where userOu.UserId==userId
                    select role.Name).ToList();
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Delete)]
        public async Task DeleteUser(EntityDto<long> input)
        {
            if (input.Id == AbpSession.GetUserId())
            {
                throw new UserFriendlyException(L("YouCanNotDeleteOwnAccount"));
            }

            var user = await UserManager.GetUserByIdAsync(input.Id);
            CheckErrors(await UserManager.DeleteAsync(user));
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Unlock)]
        public async Task UnlockUser(EntityDto<long> input)
        {
            var user = await UserManager.GetUserByIdAsync(input.Id);
            user.Unlock();
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Create,AppPermissions.Pages_Administration_Users_Update)]
      
        public async Task<ValidateOutput> ValidateUserName(ValidateInput<string, long> input)
        {
        
            if (input.Id == 0)
            {
                var one = await UserManager.Users.SingleOrDefaultAsync(r => r.UserName == input.Value);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "用户名 " + input.Value + " 已被占用"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
            else
            {
                var one = await UserManager.Users.SingleOrDefaultAsync(r =>
                    r.UserName == input.Value && r.Id != input.Id);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "用户名 " + input.Value + " 已被占用"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Create,AppPermissions.Pages_Administration_Users_Update)]
        public async Task<ValidateOutput> ValidateEmailAddress(ValidateInput<string, long> input)
        {
      
            if (input.Id == 0)
            {
                var one = await UserManager.Users.SingleOrDefaultAsync(r => r.EmailAddress == input.Value);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "Email地址 " + input.Value + " 已被占用"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
            else
            {
                var one = await UserManager.Users.SingleOrDefaultAsync(r =>
                    r.EmailAddress == input.Value && r.Id != input.Id);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "Email地址 " + input.Value + " 已被占用"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
        }
    }

    class TempUserOrganizationUnit
    {
        public User User { get; set; }
        public OrganizationUnit OrganizationUnit { get; set; }
    }
}


